package com.tron.dobest.nettysocket;

import io.netty.channel.Channel;
import io.netty.channel.ChannelId;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 *  通道管理器
 * @author TRON
 * @since 2019-12-24
 */
@Slf4j
@Component
public class TronChannelManager implements ApplicationContextAware {

    static ApplicationContext applicationContext;

    //所有channel保存在这里
    private static ChannelGroup GlobalGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    //channel的长id和短id映射
    private static ConcurrentMap<String, ChannelId> ChannelMap = new ConcurrentHashMap<>();
    //channel短id和用户id映射，一个用户可以有多个通道
    private static ConcurrentMap<String, String> userMap = new ConcurrentHashMap<>();


    /**
     * 添加连接映射
     * @param channel 通道
     */
    public static void addChannel(Channel channel,String userId) {
        String channelSortId = channel.id().asShortText();
        GlobalGroup.add(channel);
        ChannelMap.put(channelSortId, channel.id());
        userMap.put(channelSortId, userId);
    }


    /**
     * 删除连接映射，用户映射
     * @param channel 通道
     */
    public static void removeChannel(Channel channel) {
        String channelSortId = channel.id().asShortText();
        GlobalGroup.remove(channel);
        ChannelMap.remove(channelSortId);
        userMap.remove(channelSortId);
    }

    /**
     * 根据Channel短id查找连接
     * @param channelShortId 通道短id
     * @return Channel
     */
    private static Channel findChannelByShortId(String channelShortId) {
        return GlobalGroup.find(ChannelMap.get(channelShortId));
    }


    /**
     *根据用户id获取通道
     * @param userId 用户id
     * @return list
     */
    private static List<Channel> findChannelByUserId(String userId) {
        ArrayList<Channel> channels = new ArrayList<>();
        userMap.forEach((k,v)->{
            if (userId.equals(v)) {
                channels.add(findChannelByShortId(k));
            }
        });
        return channels;
    }

    /**
     * 根据用户ids查找连接列表
     * @param ids 用户id列表
     */
    private static List<Channel> findChannelByUserIds(List<String> ids) {
        ArrayList<Channel> channels = new ArrayList<>();
        userMap.forEach((k, v) -> {
            if (ids.contains(v)) {
                channels.add(findChannelByShortId(k));
            }
        });
        return channels;
    }


    /**
     * 给所有在线用户发送信息
     * @param msg 消息
     */
    public static void sendAll(String msg) {
        GlobalGroup.writeAndFlush(new TextWebSocketFrame(msg));
    }


    /**
     * 给指定用户发送消息
     * @param userId 用户id
     * @param msg 消息
     */
    public static void sendMessageByUserId(String userId, String msg) {
        List<Channel> channelByUserId = findChannelByUserId(userId);
        channelByUserId.forEach(e->{
            e.writeAndFlush(new TextWebSocketFrame(msg));
        });
    }


    /**
     * 给指定在线用户发送信息
     * @param ids 用户id列表
     * @param msg 消息
     */
    public static void sendMessageByUserIds(List<String> ids, String msg) {
        //发送消息的业务逻辑
        List<Channel> channels = findChannelByUserIds(ids);
        for (Channel channel : channels) {
            channel.writeAndFlush(new TextWebSocketFrame(msg));
        }
    }

    public static void sendBinaryTest(List<String> ids) {
        List<Channel> channels = findChannelByUserIds(ids);
        channels.forEach(c->{
            c.writeAndFlush(new BinaryWebSocketFrame());
        });
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (TronChannelManager.applicationContext == null) {
            TronChannelManager.applicationContext = applicationContext;
        }
    }
}
